package org.jjj.car.service.impl;

import org.jjj.basic.constants.BaseConstants;
import org.jjj.car.domain.CarType;
import org.jjj.car.mapper.CarTypeMapper;
import org.jjj.car.service.ICarTypeService;
import org.jjj.basic.service.impl.BaseServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author jiangjianjun
 * @since 2022-11-03
 */
@Service
public class CarTypeServiceImpl extends BaseServiceImpl<CarType> implements ICarTypeService {
    @Autowired
    private CarTypeMapper carTypeMapper;

     //注入redis，用来做缓存
    @Autowired
    private RedisTemplate redisTemplate;
    /**
     * 获取类型树
     * 一个父级下面有多个子级
     * 子级下面又有多个子级
     * 可以无限下去形成无限级树形结构
     * @return
     */
    @Override
    public List<CarType> getTree(Long pid) {
        //查询类型树在特定场合使用需要加入缓存来减少访问数据库的压力
        //1.查询数据的时候先来缓存中查询，判断是否有要获取的数据
        Object o = getCacheData(BaseConstants.CAR_TYPE_TREE_IN_REDIS);
        if (o != null) {
            //如果有直接拿到数据返回给前端
            return (List<CarType>) o;
        } else {
            //6.为了解决缓存击穿问题，在查数据库的时候放一个互斥锁
            //查询数据库每次只能允许一个请求进来查询
            synchronized (CarTypeServiceImpl.class) {
                //如果第二个进来的就可以去redis中查不用去数据库了
                o = getCacheData(BaseConstants.CAR_TYPE_TREE_IN_REDIS);
                if (o != null) {
                    //如果有直接拿到数据返回给前端
                    return (List<CarType>) o;
                }
                    //2.如果缓存中没有，先去数据库中查询
                    List<CarType> tree = getTreeByLoop(pid);
                    //3.查出来之后先在缓存中放一份
                    //5.为了解决缓存穿透问题，即使查到的是空也放到缓存中或者给缓存中设置一个默认值返回
                    if (tree != null) {
                        setCacheData(BaseConstants.CAR_TYPE_TREE_IN_REDIS,tree);
                    } else {
                        List<CarType> obj = new ArrayList<>();
                        setCacheData(BaseConstants.CAR_TYPE_TREE_IN_REDIS,obj);
                    }
                    //4.再将数据返回给前端
                    return tree;
                }
            }
        }

    /**
     * 解决缓存雪崩
     * 双key策略
     * 设置双key的值
     */
    private void setCacheData(String key,Object obj){
        //设置主key的值
        redisTemplate.opsForValue().set(key,obj);
        //设置备key的值
        redisTemplate.opsForValue().set(key+"_bak",obj);
    }
    /**
     * 获取双key中的值
     */
    private Object getCacheData(String key){
        //先查询主key
        Object obj = redisTemplate.opsForValue().get(key);
        if (Objects.nonNull(obj)){
            //如果主key中有值就返回主key的值
            return obj;
        }
        //主key没值，就查询备key
        obj = redisTemplate.opsForValue().get(key+"_bak");
        if (Objects.nonNull(obj)){
            //这里根据业务要求给主key续期
            setCacheData(key,obj);
            return obj;
        }
        return null;
    }
    /**
     * 增删改需要更新缓存
     * 方案：做完增删改后删除缓存，后面查的时候再往缓存中放入更新的数据
     */
    @Override
    public void add(CarType carType) {
        super.add(carType);
        redisTemplate.delete(BaseConstants.CAR_TYPE_TREE_IN_REDIS);
    }

    @Override
    public void deleteById(Serializable id) {
        super.deleteById(id);
        redisTemplate.delete(BaseConstants.CAR_TYPE_TREE_IN_REDIS);
    }

    @Override
    public void update(CarType carType) {
        super.update(carType);
        redisTemplate.delete(BaseConstants.CAR_TYPE_TREE_IN_REDIS);
    }

    @Override
    public void batchRemove(List<Long> ids) {
        super.batchRemove(ids);
        redisTemplate.delete(BaseConstants.CAR_TYPE_TREE_IN_REDIS);
    }

    /**
     * 获取类型树
     * 方案一：递归方法
     * 需要去数据库查询：n+1一次
     */
    private List<CarType> getTreeByRecursion(Long pid){
        //运用递归方法：方法中自己调用自己
        //顶级就是父级pid=0的类型成员
        List<CarType> children = carTypeMapper.getTree(pid);
        //递归出口
        if (Objects.isNull(children)){
            //为空就终止循环
            return children;
        }
        //不为空就遍历这一级的类型成员
        for (CarType child : children) {
            //拿到了每个类型成员，再用他们的id当做父级id：pid去查询自己底下的子集
            List<CarType> grandson = getTree(child.getId());
            //将取到的子级类型放到自己名下的子级中
            child.setChildren(grandson);
        }
        //返回类型树：子级中装着子级
        return children;
    }

    /**
     * 获取类型树
     * 方案二：去数据库查询一次，然后在内存中循环处理
     * @param pid
     * @return
     */
    private List<CarType> getTreeByLoop(Long pid){
        //1.定义一个集合来装一级类型
        List<CarType> result = new ArrayList<>();
        //2.先去数据库查出所有的类型
        List<CarType> carTypes = carTypeMapper.selectAll();
        //拿到父类型的方案二：先定义一个map用来装所有的类型，这样父类型也在其中
        //map的key是类型的id,value是整个类型对象
        HashMap<Long, CarType> map = new HashMap<>();
        carTypes.forEach(carType ->{
            map.put(carType.getId(),carType);
        } );
        //3.遍历所有的类型
        for (CarType carType : carTypes) {
            if (carType.getPid().longValue()==pid.longValue()){
                //3.1判断找出一级类型,放入返回集合中
                result.add(carType);
            }else {
                //3.2如果不是一级类型
                // 方案一：就再遍历所有类型找到该对象的父类型 时间复杂度n*n(外循环加内循环)
                /*CarType parent = null;//定义一个父类型变量
                for (CarType type : carTypes) {
                    if (carType.getPid().longValue()==type.getId().longValue()){
                        //将type作为父类型
                        parent=type;
                        break;//结束整个循环
                    }
                }*/
                //方案二：将所有类型先装到map中，然后在map中拿到父类型 时间复杂度2*n
                //从map中拿到父类型
                CarType parent = map.get(carType.getPid().longValue());
                //将自己carType装入到父类型parent的子类型children中
                parent.getChildren().add(carType);
            }
        }

        return result;
    }
}
